﻿using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace FastDFS.Client
{
    public class StorageClient
    {
        private class UploadBuff : IUploadCallback
        {
            private byte[] _fileBuff;
            private int _offset;
            private int _length;

            public UploadBuff(byte[] fileBuff, int offset, int length)
            {
                this._fileBuff = fileBuff;
                this._offset = offset;
                this._length = length;
            }

            public int Send(Stream output)
            {
                output.Write(this._fileBuff, this._offset, this._length);

                return 0;
            }
        }

        protected TrackerServer TrackerServer;
        protected StorageServer StorageServer;
        protected byte Errno;
        public static Base64 Base64 = new Base64('-', '_', '.', 0);

        public StorageClient()
        {
            this.TrackerServer = null;
            this.StorageServer = null;
        }

        /// <summary>
        /// Initiallize client
        /// </summary>
        /// <param name="trackerServer">tracker server, can not be null</param>
        /// <param name="storageServer">storage server. if be bull, get it from tracker server</param>
        public StorageClient(TrackerServer trackerServer, StorageServer storageServer)
        {
            this.TrackerServer = trackerServer;
            this.StorageServer = storageServer;
        }

        public byte ErrorCode => this.Errno;

        /// <summary>
        /// upload file to storage server (by file name)
        /// </summary>
        /// <param name="localFilename">local filename to upload</param>
        /// <param name="fileExtName">file ext name, do not include dot(.), null to extract ext name from the local filename</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file </li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>
        /// </returns>
        public async Task<string[]> UploadFileAsync(string localFilename, string fileExtName, NameValuePair[] metaList)
        {
            return await this.UploadFileAsync(null, localFilename, fileExtName, metaList);
        }

        /// <summary>
        /// upload file to storage server (by file stream)
        /// </summary>
        /// <param name="fileStream">file stream to upload</param>
        /// <param name="fileExtName">file ext name, do not include dot(.), null to extract ext name from the local filename</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file </li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>
        /// </returns>
        public async Task<string[]> UploadFileAsync(Stream fileStream, string fileExtName, NameValuePair[] metaList)
        {
            return await this.DoUploadFileAsync(ProtoCommon.StorageProtoCmdUploadFile, null, null, null, fileExtName, fileStream.Length, new UploadStream(fileStream, fileStream.Length), metaList);
        }

        /// <summary>
        /// upload file to storage server (by file name)
        /// </summary>
        /// <param name="groupName">the group name to upload file to, can be empty</param>
        /// <param name="localFilename">local filename to upload</param>
        /// <param name="fileExtName">file ext name, do not include dot(.), null to extract ext name from the local filename</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file </li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>        
        /// </returns>
        protected async Task<string[]> UploadFileAsync(string groupName, string localFilename, string fileExtName, NameValuePair[] metaList)
        {
            return await this.UploadFileAsync(ProtoCommon.StorageProtoCmdUploadFile, groupName, localFilename, fileExtName, metaList);
        }
        /// <summary>
        /// upload file to storage server (by file name)
        /// </summary>
        /// <param name="cmd">the command</param>
        /// <param name="groupName">the group name to upload file to, can be empty</param>
        /// <param name="localFilename">local filename to upload</param>
        /// <param name="fileExtName">file ext name, do not include dot(.), null to extract ext name from the local filename</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file </li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>        
        /// </returns>
        protected async Task<string[]> UploadFileAsync(byte cmd, string groupName, string localFilename, string fileExtName, NameValuePair[] metaList)
        {
            System.IO.FileInfo f = new System.IO.FileInfo(localFilename);
            FileStream fis = new FileStream(localFilename, FileMode.Open, FileAccess.Read, FileShare.Read);

            if (fileExtName == null)
            {
                fileExtName = Path.GetExtension(localFilename).TrimStart('.');
            }

            try
            {
                return await this.DoUploadFileAsync(cmd, groupName, null, null, fileExtName,
                         f.Length, new UploadStream(fis, f.Length), metaList);
            }
            finally
            {
                
                fis.Dispose();
            }
        }
        /// <summary>
        /// upload file to storage server (by file buff)
        /// </summary>
        /// <param name="fileBuff">file content/buff</param>
        /// <param name="offset">start offset of the buff</param>
        /// <param name="length">the length of buff to upload</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file</li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadFileAsync(byte[] fileBuff, int offset, int length, string fileExtName, NameValuePair[] metaList)
        {
            string groupName = null;
            return await this.UploadFileAsync(groupName, fileBuff, offset, length, fileExtName, metaList);
        }
        /// <summary>
        /// upload file to storage server (by file buff)
        /// </summary>
        /// <param name="groupName">the group name to upload file to, can be empty</param>
        /// <param name="fileBuff">file content/buff</param>
        /// <param name="offset">start offset of the buff</param>
        /// <param name="length">the length of buff to upload</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file</li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadFileAsync(string groupName, byte[] fileBuff, int offset, int length, string fileExtName, NameValuePair[] metaList)
        {
            return await this.DoUploadFileAsync(ProtoCommon.StorageProtoCmdUploadFile, groupName, null, null, fileExtName,
                       length, new UploadBuff(fileBuff, offset, length), metaList);
        }
        /// <summary>
        /// upload file to storage server (by file buff)
        /// </summary>
        /// <param name="fileBuff">file content/buff</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file</li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadFileAsync(byte[] fileBuff, string fileExtName, NameValuePair[] metaList)
        {
            string groupName = null;
            return await this.UploadFileAsync(groupName, fileBuff, 0, fileBuff.Length, fileExtName, metaList);
        }
        /// <summary>
        /// upload file to storage server (by file buff)
        /// </summary>
        /// <param name="groupName">the group name to upload file to, can be empty</param>
        /// <param name="fileBuff">file content/buff</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file</li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadFileAsync(string groupName, byte[] fileBuff, string fileExtName, NameValuePair[] metaList)
        {
            return await this.DoUploadFileAsync(ProtoCommon.StorageProtoCmdUploadFile, groupName, null, null, fileExtName,
                       fileBuff.Length, new UploadBuff(fileBuff, 0, fileBuff.Length), metaList);
        }
        /// <summary>
        /// upload file to storage server (by callback)
        /// </summary>
        /// <param name="groupName">the group name to upload file to, can be empty</param>
        /// <param name="fileSize">the file size</param>
        /// <param name="callback">the write data callback object</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file</li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadFileAsync(string groupName, long fileSize, IUploadCallback callback, string fileExtName, NameValuePair[] metaList)
        {
            return await this.DoUploadFileAsync(ProtoCommon.StorageProtoCmdUploadFile, groupName, null, null,
               fileExtName, fileSize, callback, metaList);
        }
        /// <summary>
        /// upload file to storage server (by file name, slave file mode)
        /// </summary>
        /// <param name="groupName">the group name of master file</param>
        /// <param name="masterFilename">the master file name to generate the slave file</param>
        /// <param name="prefixName">the prefix name to generate the slave file</param>
        /// <param name="localFilename">local filename to upload</param>
        /// <param name="fileExtName">file ext name, do not include dot(.), null to extract ext name from the local filename</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br/>
        /// <ul><li>results[0]: the group name to store the file</li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>
        /// </returns>
        public async Task<string[]> UploadFileAsync(string groupName, string masterFilename, string prefixName, string localFilename, string fileExtName, NameValuePair[] metaList)
        {
            if (string.IsNullOrEmpty(groupName) ||
                string.IsNullOrEmpty(masterFilename) ||
                (prefixName == null))
            {
                throw new FdfsException("invalid arguement");
            }

            System.IO.FileInfo f = new System.IO.FileInfo(localFilename);
            FileStream fis = new FileStream(localFilename, FileMode.Open, FileAccess.Read, FileShare.Read);

            if (fileExtName == null)
            {
                fileExtName = Path.GetExtension(localFilename).TrimStart('.');
            }

            try
            {
                return await this.DoUploadFileAsync(ProtoCommon.StorageProtoCmdUploadSlaveFile, groupName, masterFilename, prefixName,
                                         fileExtName, f.Length, new UploadStream(fis, f.Length), metaList);
            }
            finally
            {
                fis.Dispose();
            }
        }

        /// <summary>
        /// upload file to storage server (by file stream, slave file mode)
        /// </summary>
        /// <param name="groupName">the group name of master file</param>
        /// <param name="masterFilename">the master file name to generate the slave file</param>
        /// <param name="prefixName">the prefix name to generate the slave file</param>
        /// <param name="fileStream">stream to upload</param>
        /// <param name="fileExtName">file ext name, do not include dot(.), null to extract ext name from the local filename</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br/>
        /// <ul><li>results[0]: the group name to store the file</li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>
        /// </returns>
        public async Task<string[]> UploadFileAsync(string groupName, string masterFilename, string prefixName,
            Stream fileStream, string fileExtName, NameValuePair[] metaList)
        {
            if (string.IsNullOrEmpty(groupName) ||
                string.IsNullOrEmpty(masterFilename) ||
                (prefixName == null))
            {
                throw new FdfsException("invalid arguement");
            }

            return
                await
                    this.DoUploadFileAsync(ProtoCommon.StorageProtoCmdUploadSlaveFile, groupName, masterFilename,
                        prefixName,
                        fileExtName, fileStream.Length, new UploadStream(fileStream, fileStream.Length), metaList);
        }


        /// <summary>
        /// upload file to storage server (by file buff, slave file mode)
        /// </summary>
        /// <param name="groupName">the group name of master file</param>
        /// <param name="masterFilename">the master file name to generate the slave file</param>
        /// <param name="prefixName">the prefix name to generate the slave file</param>
        /// <param name="fileBuff">file content/buff</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file</li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadFileAsync(string groupName, string masterFilename, string prefixName, byte[] fileBuff, string fileExtName, NameValuePair[] metaList)
        {
            if (string.IsNullOrEmpty(groupName) ||
                string.IsNullOrEmpty(masterFilename) ||
                (prefixName == null))
            {
                throw new FdfsException("invalid arguement");
            }

            return await this.DoUploadFileAsync(ProtoCommon.StorageProtoCmdUploadSlaveFile, groupName, masterFilename, prefixName,
                                       fileExtName, fileBuff.Length, new UploadBuff(fileBuff, 0, fileBuff.Length), metaList);
        }
        /// <summary>
        /// upload file to storage server (by file buff, slave file mode)
        /// </summary>
        /// <param name="groupName">the group name of master file</param>
        /// <param name="masterFilename">the master file name to generate the slave file</param>
        /// <param name="prefixName">the prefix name to generate the slave file</param>
        /// <param name="fileBuff">file content/buff</param>
        /// <param name="offset">start offset of the buff</param>
        /// <param name="length">the length of buff to upload</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file</li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadFileAsync(string groupName, string masterFilename, string prefixName, byte[] fileBuff, int offset, int length, string fileExtName, NameValuePair[] metaList)
        {
            if (string.IsNullOrEmpty(groupName) ||
                string.IsNullOrEmpty(masterFilename) ||
                (prefixName == null))
            {
                throw new FdfsException("invalid arguement");
            }

            return await this.DoUploadFileAsync(ProtoCommon.StorageProtoCmdUploadSlaveFile, groupName, masterFilename, prefixName,
                        fileExtName, length, new UploadBuff(fileBuff, offset, length), metaList);
        }
        /// <summary>
        /// upload file to storage server (by file buff, slave file mode)
        /// </summary>
        /// <param name="groupName">the group name of master file</param>
        /// <param name="masterFilename">the master file name to generate the slave file</param>
        /// <param name="prefixName">the prefix name to generate the slave file</param>
        /// <param name="fileSize">the file size</param>
        /// <param name="callback">the write data callback object</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="metaList"> meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file</li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadFileAsync(string groupName, string masterFilename, string prefixName, long fileSize, IUploadCallback callback, string fileExtName, NameValuePair[] metaList)
        {
            return await this.DoUploadFileAsync(ProtoCommon.StorageProtoCmdUploadSlaveFile, groupName, masterFilename, prefixName,
               fileExtName, fileSize, callback, metaList);
        }
        /// <summary>
        /// upload appender file to storage server (by file name)
        /// </summary>
        /// <param name="localFilename">local filename to upload</param>
        /// <param name="fileExtName">file ext name, do not include dot(.), null to extract ext name from the local filename</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file </li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadAppenderFileAsync(string localFilename, string fileExtName, NameValuePair[] metaList)
        {
            string groupName = null;
            return await this.UploadAppenderFileAsync(groupName, localFilename, fileExtName, metaList);
        }
        /// <summary>
        /// upload appender file to storage server (by file name)
        /// </summary>
        /// <param name="groupName">the group name to upload file to, can be empty</param>
        /// <param name="localFilename">local filename to upload</param>
        /// <param name="fileExtName">file ext name, do not include dot(.), null to extract ext name from the local filename</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file </li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        protected async Task<string[]> UploadAppenderFileAsync(string groupName, string localFilename, string fileExtName, NameValuePair[] metaList)
        {
            byte cmd = ProtoCommon.StorageProtoCmdUploadAppenderFile;
            return await this.UploadFileAsync(cmd, groupName, localFilename, fileExtName, metaList);
        }
        /// <summary>
        /// upload appender file to storage server (by file buff)
        /// </summary>
        /// <param name="fileBuff">file content/buff</param>
        /// <param name="offset">start offset of the buff</param>
        /// <param name="length">the length of buff to upload</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file </li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadAppenderFileAsync(byte[] fileBuff, int offset, int length, string fileExtName, NameValuePair[] metaList)
        {
            string groupName = null;
            return await this.UploadAppenderFileAsync(groupName, fileBuff, offset, length, fileExtName, metaList);
        }
        /// <summary>
        /// upload appender file to storage server (by file buff)
        /// </summary>
        /// <param name="groupName">the group name to upload file to, can be empty</param>
        /// <param name="fileBuff">file content/buff</param>
        /// <param name="offset">start offset of the buff</param>
        /// <param name="length">the length of buff to upload</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file </li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadAppenderFileAsync(string groupName, byte[] fileBuff, int offset, int length, string fileExtName, NameValuePair[] metaList)
        {
            return await this.DoUploadFileAsync(ProtoCommon.StorageProtoCmdUploadAppenderFile, groupName, null, null, fileExtName,
                       length, new UploadBuff(fileBuff, offset, length), metaList);
        }
        /// <summary>
        /// upload appender file to storage server (by file buff)
        /// </summary>
        /// <param name="fileBuff">file content/buff</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file </li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadAppenderFileAsync(byte[] fileBuff, string fileExtName, NameValuePair[] metaList)
        {
            string groupName = null;
            return await this.UploadAppenderFileAsync(groupName, fileBuff, 0, fileBuff.Length, fileExtName, metaList);
        }
        /// <summary>
        /// upload appender file to storage server (by file buff)
        /// </summary>
        /// <param name="groupName">the group name to upload file to, can be empty</param>
        /// <param name="fileBuff">file content/buff</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file </li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadAppenderFileAsync(string groupName, byte[] fileBuff, string fileExtName, NameValuePair[] metaList)
        {
            return await this.DoUploadFileAsync(ProtoCommon.StorageProtoCmdUploadAppenderFile, groupName, null, null, fileExtName,
                       fileBuff.Length, new UploadBuff(fileBuff, 0, fileBuff.Length), metaList);
        }
        /// <summary>
        /// upload appender file to storage server (by callback)
        /// </summary>
        /// <param name="groupName">the group name to upload file to, can be empty</param>
        /// <param name="fileSize">the file size</param>
        /// <param name="callback">the write data callback object</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li>results[0]: the group name to store the file </li></ul>
        /// <ul><li>results[1]: the new created filename</li></ul>

        /// </returns>
        public async Task<string[]> UploadAppenderFileAsync(string groupName, long fileSize, IUploadCallback callback, string fileExtName, NameValuePair[] metaList)
        {
            string masterFilename = null;
            string prefixName = null;

            return await this.DoUploadFileAsync(ProtoCommon.StorageProtoCmdUploadAppenderFile, groupName, masterFilename, prefixName,
               fileExtName, fileSize, callback, metaList);
        }
        /// <summary>
        /// append file to storage server (by file name)
        /// </summary>
        /// <param name="groupName">the group name of appender file</param>
        /// <param name="appenderFilename">the appender filename</param>
        /// <param name="localFilename">local filename to append</param>
        /// <returns>0 for success, != 0 for error (error no)</returns>
        public async Task<int> AppendFileAsync(string groupName, string appenderFilename, string localFilename)
        {
            System.IO.FileInfo f = new System.IO.FileInfo(localFilename);
            FileStream fis = new FileStream(localFilename, FileMode.Open, FileAccess.Read, FileShare.Read);

            try
            {
                return await this.DoAppendFileAsync(groupName, appenderFilename, f.Length, new UploadStream(fis, f.Length));
            }
            finally
            {
                fis.Dispose();
            }
        }
        /// <summary>
        /// append file to storage server (by file buff)
        /// </summary>
        /// <param name="groupName">the group name of appender file</param>
        /// <param name="appenderFilename">the appender filename</param>
        /// <param name="fileBuff">file content/buff</param>
        /// <returns>0 for success, != 0 for error (error no)</returns>
        public async Task<int> AppendFileAsync(string groupName, string appenderFilename, byte[] fileBuff)
        {
            return await this.DoAppendFileAsync(groupName, appenderFilename, fileBuff.Length, new UploadBuff(fileBuff, 0, fileBuff.Length));
        }
        /// <summary>
        /// append file to storage server (by file buff)
        /// </summary>
        /// <param name="groupName">the group name of appender file</param>
        /// <param name="appenderFilename">the appender filename</param>
        /// <param name="fileBuff">file content/buff</param>
        /// <param name="offset">start offset of the buff</param>
        /// <param name="length">the length of buff to append</param>
        /// <returns>0 for success, != 0 for error (error no)</returns>
        public async Task<int> AppendFileAsync(string groupName, string appenderFilename, byte[] fileBuff, int offset, int length)
        {
            return await this.DoAppendFileAsync(groupName, appenderFilename, length, new UploadBuff(fileBuff, offset, length));
        }
        /// <summary>
        /// append file to storage server (by callback)
        /// </summary>
        /// <param name="groupName">the group name to append file to</param>
        /// <param name="appenderFilename">the appender filename</param>
        /// <param name="fileSize">the file size</param>
        /// <param name="callback">the write data callback object</param>
        /// <returns>0 for success, != 0 for error (error no)</returns>
        public async Task<int> AppendFileAsync(string groupName, string appenderFilename, long fileSize, IUploadCallback callback)
        {
            return await this.DoAppendFileAsync(groupName, appenderFilename, fileSize, callback);
        }
        /// <summary>
        /// modify appender file to storage server (by file name)
        /// </summary>
        /// <param name="groupName">the group name of appender file</param>
        /// <param name="appenderFilename">the appender filename</param>
        /// <param name="fileOffset">the offset of appender file</param>
        /// <param name="localFilename">local filename to append</param>
        /// <returns>0 for success, != 0 for error (error no)</returns>
        public async Task<int> ModifyFileAsync(string groupName, string appenderFilename, long fileOffset, string localFilename)
        {
            System.IO.FileInfo f = new System.IO.FileInfo(localFilename);
            FileStream fis = new FileStream(localFilename, FileMode.Open, FileAccess.Read, FileShare.Read);

            try
            {
                return await this.DoModifyFileAsync(groupName, appenderFilename, fileOffset,
                      f.Length, new UploadStream(fis, f.Length));
            }
            finally
            {
                fis.Dispose();
            }
        }
        /// <summary>
        /// modify appender file to storage server (by file buff)
        /// </summary>
        /// <param name="groupName">the group name of appender file</param>
        /// <param name="appenderFilename">the appender filename</param>
        /// <param name="fileOffset">the offset of appender file</param>
        /// <param name="fileBuff">file content/buff</param>
        /// <returns>0 for success, != 0 for error (error no)</returns>
        public async Task<int> ModifyFileAsync(string groupName, string appenderFilename, long fileOffset, byte[] fileBuff)
        {
            return await this.DoModifyFileAsync(groupName, appenderFilename, fileOffset,
                    fileBuff.Length, new UploadBuff(fileBuff, 0, fileBuff.Length));
        }
        /// <summary>
        /// modify appender file to storage server (by file buff)
        /// </summary>
        /// <param name="groupName">the group name of appender file</param>
        /// <param name="appenderFilename">the appender filename</param>
        /// <param name="fileOffset">the offset of appender file</param>
        /// <param name="fileBuff">file content/buff</param>
        /// <param name="bufferOffset">start offset of the buff</param>
        /// <param name="bufferLength">the length of buff to modify</param>
        /// <returns>0 for success, != 0 for error (error no)</returns>
        public async Task<int> ModifyFileAsync(string groupName, string appenderFilename, long fileOffset, byte[] fileBuff, int bufferOffset, int bufferLength)
        {
            return await this.DoModifyFileAsync(groupName, appenderFilename, fileOffset,
                    bufferLength, new UploadBuff(fileBuff, bufferOffset, bufferLength));
        }
        /// <summary>
        /// modify appender file to storage server (by callback)
        /// </summary>
        /// <param name="groupName">the group name to modify file to</param>
        /// <param name="appenderFilename">the appender filename</param>
        /// <param name="fileOffset">the offset of appender file</param>
        /// <param name="modifySize">the modify size</param>
        /// <param name="callback">the write data callback object</param>
        /// <returns>0 for success, != 0 for error (error no)</returns>
        public async Task<int> ModifyFileAsync(string groupName, string appenderFilename, long fileOffset, long modifySize, IUploadCallback callback)
        {
            return await this.DoModifyFileAsync(groupName, appenderFilename, fileOffset,
                    modifySize, callback);
        }
        /// <summary>
        /// modify appender file to storage server
        /// </summary>
        /// <param name="groupName">the group name of appender file</param>
        /// <param name="appenderFilename">the appender filename</param>
        /// <param name="fileOffset">the offset of appender file</param>
        /// <param name="modifySize">the modify size</param>
        /// <param name="callback">the write data callback object</param>
        /// <returns>true for success, false for fail</returns>
        protected async Task<int> DoModifyFileAsync(string groupName, string appenderFilename, long fileOffset, long modifySize, IUploadCallback callback)
        {
            byte[] header;
            bool bNewConnection;
            TcpClient storageSocket;
            byte[] hexLenBytes;
            byte[] appenderFilenameBytes;
            int offset;
            long bodyLen;

            if (string.IsNullOrEmpty(groupName) || string.IsNullOrEmpty(appenderFilename))
            {
                this.Errno = ProtoCommon.ErrNoEinval;
                return this.Errno;
            }

            bNewConnection = await this.NewUpdatableStorageConnectionAsync(groupName, appenderFilename);

            try
            {
                storageSocket = await this.StorageServer.GetSocketAsync();

                appenderFilenameBytes = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(appenderFilename);
                bodyLen = 3 * ProtoCommon.FdfsProtoPkgLenSize + appenderFilenameBytes.Length + modifySize;

                header = ProtoCommon.PackHeader(ProtoCommon.StorageProtoCmdModifyFile, bodyLen, (byte)0);
                byte[] wholePkg = new byte[(int)(header.Length + bodyLen - modifySize)];
                Array.Copy(header, 0, wholePkg, 0, header.Length);
                offset = header.Length;

                hexLenBytes = ProtoCommon.Long2Buff(appenderFilename.Length);
                Array.Copy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.Length);
                offset += hexLenBytes.Length;

                hexLenBytes = ProtoCommon.Long2Buff(fileOffset);
                Array.Copy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.Length);
                offset += hexLenBytes.Length;

                hexLenBytes = ProtoCommon.Long2Buff(modifySize);
                Array.Copy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.Length);
                offset += hexLenBytes.Length;

                Stream output = storageSocket.GetStream();

                Array.Copy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.Length);
                offset += appenderFilenameBytes.Length;

                output.Write(wholePkg, 0, wholePkg.Length);
                if ((this.Errno = (byte)callback.Send(output)) != 0)
                {
                    return this.Errno;
                }

                ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.RecvPackage(storageSocket.GetStream(),
                                             ProtoCommon.StorageProtoCmdResp, 0);
                this.Errno = pkgInfo.Errno;
                if (pkgInfo.Errno != 0)
                {
                    return this.Errno;
                }

                return 0;
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    this.StorageServer.Close();
                    this.StorageServer = null;
                }

                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    this.StorageServer.Close();
                    this.StorageServer = null;
                }
            }
        }
        /// <summary>
        /// append file to storage server
        /// </summary>
        /// <param name="groupName">the group name of appender file</param>
        /// <param name="appenderFilename">the appender filename</param>
        /// <param name="fileSize">the file size</param>
        /// <param name="callback">the write data callback object</param>
        /// <returns>true for success, false for fail</returns>
        protected async Task<int> DoAppendFileAsync(string groupName, string appenderFilename, long fileSize, IUploadCallback callback)
        {
            byte[] header;
            bool bNewConnection;
            TcpClient storageSocket;
            byte[] hexLenBytes;
            byte[] appenderFilenameBytes;
            int offset;
            long bodyLen;

            if (string.IsNullOrEmpty(groupName) || string.IsNullOrEmpty(appenderFilename))
            {
                this.Errno = ProtoCommon.ErrNoEinval;
                return this.Errno;
            }

            bNewConnection = await this.NewUpdatableStorageConnectionAsync(groupName, appenderFilename);

            try
            {
                storageSocket = await this.StorageServer.GetSocketAsync();

                appenderFilenameBytes = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(appenderFilename);
                bodyLen = 2 * ProtoCommon.FdfsProtoPkgLenSize + appenderFilenameBytes.Length + fileSize;

                header = ProtoCommon.PackHeader(ProtoCommon.StorageProtoCmdAppendFile, bodyLen, (byte)0);
                byte[] wholePkg = new byte[(int)(header.Length + bodyLen - fileSize)];
                Array.Copy(header, 0, wholePkg, 0, header.Length);
                offset = header.Length;

                //hexLenBytes = BitConverter.GetBytes((long)appender_filename.Length);
                hexLenBytes = ProtoCommon.Long2Buff(appenderFilename.Length);
                Array.Copy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.Length);
                offset += hexLenBytes.Length;

                //hexLenBytes = BitConverter.GetBytes(file_size);
                hexLenBytes = ProtoCommon.Long2Buff(fileSize);
                Array.Copy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.Length);
                offset += hexLenBytes.Length;

                Stream output = storageSocket.GetStream();

                Array.Copy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.Length);
                offset += appenderFilenameBytes.Length;

                output.Write(wholePkg, 0, wholePkg.Length);
                if ((this.Errno = (byte)callback.Send(output)) != 0)
                {
                    return this.Errno;
                }

                ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.RecvPackage(storageSocket.GetStream(),
                                             ProtoCommon.StorageProtoCmdResp, 0);
                this.Errno = pkgInfo.Errno;
                if (pkgInfo.Errno != 0)
                {
                    return this.Errno;
                }

                return 0;
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    try
                    {
                        this.StorageServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                    finally
                    {
                        this.StorageServer = null;
                    }
                }

                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    this.StorageServer.Close();
                    this.StorageServer = null;
                }
            }
        }
        /// <summary>
        /// upload file to storage server
        /// </summary>
        /// <param name="cmd">the command code</param>
        /// <param name="groupName">the group name to upload file to, can be empty</param>
        /// <param name="masterFilename">the master file name to generate the slave file</param>
        /// <param name="prefixName">the prefix name to generate the slave file</param>
        /// <param name="fileExtName">file ext name, do not include dot(.)</param>
        /// <param name="fileSize">the file size</param>
        /// <param name="callback">the write data callback object</param>
        /// <param name="metaList">meta info array</param>
        /// <returns>
        /// 2 elements string array if success:<br>
        /// <ul><li> results[0]: the group name to store the file</li></ul>
        /// <ul><li> results[1]: the new created filename</li></ul> 

        /// </returns>
        protected async Task<string[]> DoUploadFileAsync(byte cmd, string groupName, string masterFilename, string prefixName, string fileExtName, long fileSize, IUploadCallback callback, NameValuePair[] metaList)
        {
            byte[] header;
            byte[] extNameBs;
            string newGroupName;
            string remoteFilename;
            bool bNewConnection = false;
            TcpClient storageSocket;
            byte[] sizeBytes;
            byte[] hexLenBytes;
            byte[] masterFilenameBytes;
            bool bUploadSlave;
            int offset;
            long bodyLen;

            bUploadSlave = !string.IsNullOrEmpty(groupName) &&
                           !string.IsNullOrEmpty(masterFilename) &&
                           (prefixName != null);
            if (bUploadSlave)
            {
                bNewConnection = await this.NewUpdatableStorageConnectionAsync(groupName, masterFilename);
            }
            else
            {
                bNewConnection = await this.NewWritableStorageConnectionAsync(groupName);
            }

            try
            {
                storageSocket = await this.StorageServer.GetSocketAsync();

                extNameBs = new byte[ProtoCommon.FdfsFileExtNameMaxLen];

                for (int i = 0; i < extNameBs.Length; i++) extNameBs[i] = (byte)0;

                if (!string.IsNullOrEmpty(fileExtName))
                {
                    byte[] bs = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(fileExtName);
                    int extNameLen = bs.Length;
                    if (extNameLen > ProtoCommon.FdfsFileExtNameMaxLen)
                    {
                        extNameLen = ProtoCommon.FdfsFileExtNameMaxLen;
                    }
                    Array.Copy(bs, 0, extNameBs, 0, extNameLen);
                }

                if (bUploadSlave)
                {
                    masterFilenameBytes = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(masterFilename);

                    sizeBytes = new byte[2 * ProtoCommon.FdfsProtoPkgLenSize];
                    bodyLen = sizeBytes.Length + ProtoCommon.FdfsFilePrefixMaxLen + ProtoCommon.FdfsFileExtNameMaxLen
                             + masterFilenameBytes.Length + fileSize;

                    hexLenBytes = ProtoCommon.Long2Buff(masterFilename.Length);
                    Array.Copy(hexLenBytes, 0, sizeBytes, 0, hexLenBytes.Length);
                    offset = hexLenBytes.Length;
                }
                else
                {
                    masterFilenameBytes = null;
                    sizeBytes = new byte[1 + 1 * ProtoCommon.FdfsProtoPkgLenSize];
                    bodyLen = sizeBytes.Length + ProtoCommon.FdfsFileExtNameMaxLen + fileSize;

                    sizeBytes[0] = (byte)this.StorageServer.StorePathIndex;
                    offset = 1;
                }

                hexLenBytes = ProtoCommon.Long2Buff(fileSize);
                Array.Copy(hexLenBytes, 0, sizeBytes, offset, hexLenBytes.Length);

                Stream output = storageSocket.GetStream();
                header = ProtoCommon.PackHeader(cmd, bodyLen, (byte)0);
                byte[] wholePkg = new byte[(int)(header.Length + bodyLen - fileSize)];
                Array.Copy(header, 0, wholePkg, 0, header.Length);
                Array.Copy(sizeBytes, 0, wholePkg, header.Length, sizeBytes.Length);
                offset = header.Length + sizeBytes.Length;
                if (bUploadSlave)
                {
                    byte[] prefixNameBs = new byte[ProtoCommon.FdfsFilePrefixMaxLen];
                    byte[] bs = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(prefixName);
                    int prefixNameLen = bs.Length;
                    for (int i = 0; i < prefixNameBs.Length; i++)
                        prefixNameBs[i] = (byte)0;
                    if (prefixNameLen > ProtoCommon.FdfsFilePrefixMaxLen)
                    {
                        prefixNameLen = ProtoCommon.FdfsFilePrefixMaxLen;
                    }
                    if (prefixNameLen > 0)
                    {
                        Array.Copy(bs, 0, prefixNameBs, 0, prefixNameLen);
                    }

                    Array.Copy(prefixNameBs, 0, wholePkg, offset, prefixNameBs.Length);
                    offset += prefixNameBs.Length;
                }

                Array.Copy(extNameBs, 0, wholePkg, offset, extNameBs.Length);
                offset += extNameBs.Length;

                if (bUploadSlave)
                {
                    Array.Copy(masterFilenameBytes, 0, wholePkg, offset, masterFilenameBytes.Length);
                    offset += masterFilenameBytes.Length;
                }

                output.Write(wholePkg, 0, wholePkg.Length);

                if ((this.Errno = (byte)callback.Send(output)) != 0)
                {
                    throw new FdfsException("send file error:" + this.Errno);
                }

                ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.RecvPackage(storageSocket.GetStream(),
                                             ProtoCommon.StorageProtoCmdResp, -1);
                this.Errno = pkgInfo.Errno;
                if (pkgInfo.Errno != 0)
                {
                    throw new FdfsException("send file response error:" + pkgInfo.Errno);
                }

                if (pkgInfo.Body.Length <= ProtoCommon.FdfsGroupNameMaxLen)
                {
                    throw new FdfsException("body length: " + pkgInfo.Body.Length + " <= " + ProtoCommon.FdfsGroupNameMaxLen);
                }

                newGroupName = Encoding.GetEncoding(ClientGlobal.Charset).GetString(pkgInfo.Body, 0, ProtoCommon.FdfsGroupNameMaxLen).Replace("\0", "").Trim();
                remoteFilename = Encoding.GetEncoding(ClientGlobal.Charset).GetString(pkgInfo.Body, ProtoCommon.FdfsGroupNameMaxLen, pkgInfo.Body.Length - ProtoCommon.FdfsGroupNameMaxLen);
                string[] results = new string[2];
                results[0] = newGroupName;
                results[1] = remoteFilename;

                if (metaList == null || metaList.Length == 0)
                {
                    return results;
                }

                int result = 0;
                try
                {
                    result = await this.SetMetadataAsync(newGroupName, remoteFilename,
                                    metaList, ProtoCommon.StorageSetMetadataFlagOverwrite);
                }
                catch (IOException)
                {
                    result = 5;
                    throw;
                }
                finally
                {
                    if (result != 0)
                    {
                        this.Errno = (byte)result;
                        await this.DeleteFileAsync(newGroupName, remoteFilename);
                    }
                }

                return results;
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    try
                    {
                        this.StorageServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                    finally
                    {
                        this.StorageServer = null;
                    }
                }

                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    this.StorageServer.Close();
                    this.StorageServer = null;
                }
            }
        }
        /// <summary>
        /// check storage socket, if null create a new connection
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        /// <returns>true if create a new connection</returns>
        protected async Task<bool> NewUpdatableStorageConnectionAsync(string groupName, string remoteFilename)
        {
            if (this.StorageServer != null)
            {
                return false;
            }
            else
            {
                TrackerClient tracker = new TrackerClient();
                this.StorageServer = await tracker.GetUpdateStorageAsync(this.TrackerServer, groupName, remoteFilename);
                if (this.StorageServer == null)
                {
                    throw new FdfsException("getStoreStorage fail, errno code: " + tracker.ErrorCode);
                }
                return true;
            }
        }
        /// <summary>
        /// check storage socket, if null create a new connection
        /// </summary>
        /// <param name="groupName">the group name to upload file to, can be empty</param>
        /// <returns>true if create a new connection</returns>
        protected async Task<bool> NewWritableStorageConnectionAsync(string groupName)
        {
            if (this.StorageServer != null)
            {
                return false;
            }
            TrackerClient tracker = new TrackerClient();
            this.StorageServer = await tracker.GetStoreStorageAsync(this.TrackerServer, groupName);
            if (this.StorageServer == null)
            {
                throw new FdfsException("getStoreStorage fail, errno code: " + tracker.ErrorCode);
            }
            return true;
        }
        /// <summary>
        /// set metadata items to storage server
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        /// <param name="metaList">meta item array</param>
        /// <param name="opFlag">
        /// flag, can be one of following values: <br>
        /// <ul><li> ProtoCommon.STORAGE_SET_METADATA_FLAG_OVERWRITE: overwrite all old metadata items</li></ul>
        /// <ul><li> ProtoCommon.STORAGE_SET_METADATA_FLAG_MERGE: merge, insert when the metadata item not exist, otherwise update it</li></ul>
        /// </param>
        /// <returns>0 for success, !=0 fail (error code)</returns>
        public async Task<int> SetMetadataAsync(string groupName, string remoteFilename, NameValuePair[] metaList, byte opFlag)
        {
            bool bNewConnection = await this.NewUpdatableStorageConnectionAsync(groupName, remoteFilename);
            TcpClient storageSocket = await this.StorageServer.GetSocketAsync();

            try
            {
                byte[] header;
                byte[] groupBytes;
                byte[] filenameBytes;
                byte[] metaBuff;
                byte[] bs;
                int groupLen;
                byte[] sizeBytes;
                ProtoCommon.RecvPackageInfo pkgInfo;

                if (metaList == null)
                {
                    metaBuff = new byte[0];
                }
                else
                {
                    metaBuff = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(ProtoCommon.PackMetadata(metaList));
                }

                filenameBytes = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(remoteFilename);
                sizeBytes = new byte[2 * ProtoCommon.FdfsProtoPkgLenSize];
                for (int i = 0; i < sizeBytes.Length; i++)
                    sizeBytes[i] = (byte)0;

                //bs = BitConverter.GetBytes((long)filenameBytes.Length);
                bs = ProtoCommon.Long2Buff(filenameBytes.Length);
                Array.Copy(bs, 0, sizeBytes, 0, bs.Length);
                //bs = BitConverter.GetBytes((long)meta_buff.Length);
                bs = ProtoCommon.Long2Buff(metaBuff.Length);
                Array.Copy(bs, 0, sizeBytes, ProtoCommon.FdfsProtoPkgLenSize, bs.Length);

                groupBytes = new byte[ProtoCommon.FdfsGroupNameMaxLen];
                bs = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(groupName);
                for (int i = 0; i < groupBytes.Length; i++)
                    groupBytes[i] = (byte)0;
                if (bs.Length <= groupBytes.Length)
                {
                    groupLen = bs.Length;
                }
                else
                {
                    groupLen = groupBytes.Length;
                }
                Array.Copy(bs, 0, groupBytes, 0, groupLen);

                header = ProtoCommon.PackHeader(ProtoCommon.StorageProtoCmdSetMetadata,
                           2 * ProtoCommon.FdfsProtoPkgLenSize + 1 + groupBytes.Length
                           + filenameBytes.Length + metaBuff.Length, (byte)0);
                Stream output = storageSocket.GetStream();
                byte[] wholePkg = new byte[header.Length + sizeBytes.Length + 1 + groupBytes.Length + filenameBytes.Length];
                Array.Copy(header, 0, wholePkg, 0, header.Length);
                Array.Copy(sizeBytes, 0, wholePkg, header.Length, sizeBytes.Length);
                wholePkg[header.Length + sizeBytes.Length] = opFlag;
                Array.Copy(groupBytes, 0, wholePkg, header.Length + sizeBytes.Length + 1, groupBytes.Length);
                Array.Copy(filenameBytes, 0, wholePkg, header.Length + sizeBytes.Length + 1 + groupBytes.Length, filenameBytes.Length);
                output.Write(wholePkg, 0, wholePkg.Length);
                if (metaBuff.Length > 0)
                {
                    output.Write(metaBuff, 0, metaBuff.Length);
                }

                pkgInfo = ProtoCommon.RecvPackage(storageSocket.GetStream(),
                                             ProtoCommon.StorageProtoCmdResp, 0);

                this.Errno = pkgInfo.Errno;
                return pkgInfo.Errno;
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    try
                    {
                        this.StorageServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                    finally
                    {
                        this.StorageServer = null;
                    }
                }

                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    try
                    {
                        this.StorageServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                    finally
                    {
                        this.StorageServer = null;
                    }
                }
            }
        }
        /// <summary>
        /// delete file from storage server
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        /// <returns>0 for success, none zero for fail (error code)</returns>
        public async Task<int> DeleteFileAsync(string groupName, string remoteFilename)
        {
            bool bNewConnection = await this.NewUpdatableStorageConnectionAsync(groupName, remoteFilename);
            TcpClient storageSocket = await this.StorageServer.GetSocketAsync();

            try
            {
                await this.SendPackageAsync(ProtoCommon.StorageProtoCmdDeleteFile, groupName, remoteFilename);
                ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.RecvPackage(storageSocket.GetStream(),
                                             ProtoCommon.StorageProtoCmdResp, 0);

                this.Errno = pkgInfo.Errno;
                return pkgInfo.Errno;
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    try
                    {
                        this.StorageServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                    finally
                    {
                        this.StorageServer = null;
                    }
                }

                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    try
                    {
                        this.StorageServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                    finally
                    {
                        this.StorageServer = null;
                    }
                }
            }
        }
        /// <summary>
        /// send package to storage server
        /// </summary>
        /// <param name="cmd">which command to send</param>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        protected async Task SendPackageAsync(byte cmd, string groupName, string remoteFilename)
        {
            byte[] header;
            byte[] groupBytes;
            byte[] filenameBytes;
            byte[] bs;
            int groupLen;

            groupBytes = new byte[ProtoCommon.FdfsGroupNameMaxLen];
            bs = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(groupName);
            filenameBytes = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(remoteFilename);
            for (int i = 0; i < groupBytes.Length; i++)
                groupBytes[i] = (byte)0;
            if (bs.Length <= groupBytes.Length)
            {
                groupLen = bs.Length;
            }
            else
            {
                groupLen = groupBytes.Length;
            }
            Array.Copy(bs, 0, groupBytes, 0, groupLen);

            header = ProtoCommon.PackHeader(cmd, groupBytes.Length + filenameBytes.Length, (byte)0);
            byte[] wholePkg = new byte[header.Length + groupBytes.Length + filenameBytes.Length];
            Array.Copy(header, 0, wholePkg, 0, header.Length);
            Array.Copy(groupBytes, 0, wholePkg, header.Length, groupBytes.Length);
            Array.Copy(filenameBytes, 0, wholePkg, header.Length + groupBytes.Length, filenameBytes.Length);
            (await this.StorageServer.GetSocketAsync()).GetStream().Write(wholePkg, 0, wholePkg.Length);
        }
        /// <summary>
        /// truncate appender file to size 0 from storage server
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="appenderFilename">the appender filename</param>
        /// <returns>0 for success, none zero for fail (error code)</returns>
        public async Task<int> TruncateFileAsync(string groupName, string appenderFilename)
        {
            return await this.TruncateFileAsync(groupName, appenderFilename, 0);
        }
        /// <summary>
        /// truncate appender file from storage server
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="appenderFilename">the appender filename</param>
        /// <param name="truncatedFileSize">truncated file size</param>
        /// <returns>0 for success, none zero for fail (error code)</returns>
        public async Task<int> TruncateFileAsync(string groupName, string appenderFilename, long truncatedFileSize)
        {
            byte[] header;
            bool bNewConnection;
            TcpClient storageSocket;
            byte[] hexLenBytes;
            byte[] appenderFilenameBytes;
            int offset;
            int bodyLen;

            if (string.IsNullOrEmpty(groupName) ||
                string.IsNullOrEmpty(appenderFilename))
            {
                this.Errno = ProtoCommon.ErrNoEinval;
                return this.Errno;
            }

            bNewConnection = await this.NewUpdatableStorageConnectionAsync(groupName, appenderFilename);

            try
            {
                storageSocket = await this.StorageServer.GetSocketAsync();

                appenderFilenameBytes = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(appenderFilename);
                bodyLen = 2 * ProtoCommon.FdfsProtoPkgLenSize + appenderFilenameBytes.Length;

                header = ProtoCommon.PackHeader(ProtoCommon.StorageProtoCmdTruncateFile, bodyLen, (byte)0);
                byte[] wholePkg = new byte[header.Length + bodyLen];
                Array.Copy(header, 0, wholePkg, 0, header.Length);
                offset = header.Length;

                //hexLenBytes = BitConverter.GetBytes((long)appender_filename.Length);
                hexLenBytes = ProtoCommon.Long2Buff(appenderFilename.Length);
                Array.Copy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.Length);
                offset += hexLenBytes.Length;

                //hexLenBytes = BitConverter.GetBytes(truncated_file_size);
                hexLenBytes = ProtoCommon.Long2Buff(truncatedFileSize);
                Array.Copy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.Length);
                offset += hexLenBytes.Length;

                Stream output = storageSocket.GetStream();

                Array.Copy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.Length);
                offset += appenderFilenameBytes.Length;

                output.Write(wholePkg, 0, wholePkg.Length);
                ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.RecvPackage(storageSocket.GetStream(),
                                             ProtoCommon.StorageProtoCmdResp, 0);
                this.Errno = pkgInfo.Errno;
                return pkgInfo.Errno;
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    try
                    {
                        this.StorageServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                    finally
                    {
                        this.StorageServer = null;
                    }
                }

                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    try
                    {
                        this.StorageServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                    finally
                    {
                        this.StorageServer = null;
                    }
                }
            }
        }
        /// <summary>
        /// download file from storage server
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        /// <returns>file content/buff</returns>
        public async Task<byte[]> DownloadFileAsync(string groupName, string remoteFilename)
        {
            long fileOffset = 0;
            long downloadBytes = 0;

            return await this.DownloadFileAsync(groupName, remoteFilename, fileOffset, downloadBytes);
        }
        /// <summary>
        /// download file from storage server
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        /// <param name="fileOffset">the start offset of the file</param>
        /// <param name="downloadBytes">download bytes, 0 for remain bytes from offset</param>
        /// <returns>file content/buff</returns>
        public async Task<byte[]> DownloadFileAsync(string groupName, string remoteFilename, long fileOffset, long downloadBytes)
        {
            bool bNewConnection = await this.NewReadableStorageConnectionAsync(groupName, remoteFilename);
            TcpClient storageSocket = await this.StorageServer.GetSocketAsync();

            try
            {
                await this.SendDownloadPackageAsync(groupName, remoteFilename, fileOffset, downloadBytes);
                var pkgInfo = ProtoCommon.RecvPackage(storageSocket.GetStream(),
                    ProtoCommon.StorageProtoCmdResp, -1);

                this.Errno = pkgInfo.Errno;
                if (pkgInfo.Errno != 0)
                {
                    throw new FdfsException("download file response error: " + pkgInfo.Errno);
                }

                return pkgInfo.Body;
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    try
                    {
                        this.StorageServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                    finally
                    {
                        this.StorageServer = null;
                    }
                }

                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    try
                    {
                        this.StorageServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                    finally
                    {
                        this.StorageServer = null;
                    }
                }
            }
        }
        /// <summary>
        /// download file from storage server
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        /// <param name="localFilename">filename on local</param>
        /// <returns>0 success, return none zero errno if fail</returns>
        public async Task<int> DownloadFileAsync(string groupName, string remoteFilename, string localFilename)
        {
            return await this.DownloadFileAsync(groupName, remoteFilename,
                          0, 0, localFilename);
        }
        /// <summary>
        /// download file from storage server
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        /// <param name="fileOffset">the start offset of the file</param>
        /// <param name="downloadBytes">download bytes, 0 for remain bytes from offset</param>
        /// <param name="localFilename">filename on local</param>
        /// <returns>0 success, return none zero errno if fail</returns>
        public async Task<int> DownloadFileAsync(string groupName, string remoteFilename, long fileOffset, long downloadBytes, string localFilename)
        {
            bool bNewConnection = await this.NewReadableStorageConnectionAsync(groupName, remoteFilename);
            TcpClient storageSocket = await this.StorageServer.GetSocketAsync();
            try
            {
                ProtoCommon.RecvHeaderInfo header;
                FileStream output = new FileStream(localFilename, FileMode.Create, FileAccess.Write, FileShare.Read);
                try
                {
                    this.Errno = 0;
                    await this.SendDownloadPackageAsync(groupName, remoteFilename, fileOffset, downloadBytes);

                    Stream input = storageSocket.GetStream();
                    header = ProtoCommon.RecvHeader(input, ProtoCommon.StorageProtoCmdResp, -1);
                    this.Errno = header.Errno;
                    if (header.Errno != 0)
                    {
                        return header.Errno;
                    }

                    byte[] buff = new byte[256 * 1024];
                    long remainBytes = header.BodyLen;
                    int bytes;

                    //System.out.println("expect_body_len=" + header.body_len);

                    while (remainBytes > 0)
                    {
                        if ((bytes = input.Read(buff, 0, remainBytes > buff.Length ? buff.Length : (int)remainBytes)) < 0)
                        {
                            throw new IOException("recv package size " + (header.BodyLen - remainBytes) + " != " + header.BodyLen);
                        }

                        output.Write(buff, 0, bytes);
                        remainBytes -= bytes;

                        //System.out.println("totalBytes=" + (header.body_len - remainBytes));
                    }

                    return 0;
                }
                catch (IOException)
                {
                    if (this.Errno == 0)
                    {
                        this.Errno = ProtoCommon.ErrNoEio;
                    }

                    throw;
                }
                finally
                {
                    output.Dispose();
                    if (this.Errno != 0)
                    {
                        File.Delete(localFilename);
                    }
                }
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    try
                    {
                        this.StorageServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                    finally
                    {
                        this.StorageServer = null;
                    }
                }

                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    try
                    {
                        this.StorageServer.Close();
                    }
                    catch (IOException)
                    {

                    }
                    finally
                    {
                        this.StorageServer = null;
                    }
                }
            }
        }
        /// <summary>
        /// download file from storage server
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        /// <param name="callback">call callback.recv() when data arrive</param>
        /// <returns>0 success, return none zero errno if fail</returns>
        public async Task<int> DownloadFileAsync(string groupName, string remoteFilename, IDownloadCallback callback)
        {
            return await this.DownloadFileAsync(groupName, remoteFilename,
                          0, 0, callback);
        }
        /// <summary>
        /// download file from storage server
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        /// <param name="fileOffset">the start offset of the file</param>
        /// <param name="downloadBytes">download bytes, 0 for remain bytes from offset</param>
        /// <param name="callback">call callback.recv() when data arrive</param>
        /// <returns>0 success, return none zero errno if fail</returns>
        public async Task<int> DownloadFileAsync(string groupName, string remoteFilename, long fileOffset, long downloadBytes, IDownloadCallback callback)
        {
            int result;
            bool bNewConnection = await this.NewReadableStorageConnectionAsync(groupName, remoteFilename);
            TcpClient storageSocket = await this.StorageServer.GetSocketAsync();

            try
            {
                ProtoCommon.RecvHeaderInfo header;
                await this.SendDownloadPackageAsync(groupName, remoteFilename, fileOffset, downloadBytes);

                Stream input = storageSocket.GetStream();
                header = ProtoCommon.RecvHeader(input, ProtoCommon.StorageProtoCmdResp, -1);
                this.Errno = header.Errno;
                if (header.Errno != 0)
                {
                    return header.Errno;
                }

                byte[] buff = new byte[2 * 1024];
                long remainBytes = header.BodyLen;
                int bytes;

                while (remainBytes > 0)
                {
                    if ((bytes = input.Read(buff, 0, remainBytes > buff.Length ? buff.Length : (int)remainBytes)) < 0)
                    {
                        throw new IOException("recv package size " + (header.BodyLen - remainBytes) + " != " + header.BodyLen);
                    }

                    if ((result = callback.Recv(header.BodyLen, buff, bytes)) != 0)
                    {
                        this.Errno = (byte)result;
                        return result;
                    }

                    remainBytes -= bytes;
                }

                return 0;
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    this.StorageServer.Close();
                    this.StorageServer = null;
                }

                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    this.StorageServer.Close();
                    this.StorageServer = null;
                }
            }
        }
        /// <summary>
        /// check storage socket, if null create a new connection
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        /// <returns>true if create a new connection</returns>
        protected async Task<bool> NewReadableStorageConnectionAsync(string groupName, string remoteFilename)
        {
            if (this.StorageServer != null)
            {
                return false;
            }
            else
            {
                TrackerClient tracker = new TrackerClient();
                this.StorageServer = await tracker.GetFetchStorageAsync(this.TrackerServer, groupName, remoteFilename);
                if (this.StorageServer == null)
                {
                    throw new FdfsException("getStoreStorage fail, errno code: " + tracker.ErrorCode);
                }
                return true;
            }
        }
        /// <summary>
        /// send package to storage server
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        /// <param name="fileOffset">the start offset of the file</param>
        /// <param name="downloadBytes">download bytes</param>
        protected async Task SendDownloadPackageAsync(string groupName, string remoteFilename, long fileOffset, long downloadBytes)
        {
            byte[] header;
            byte[] bsOffset;
            byte[] bsDownBytes;
            byte[] groupBytes;
            byte[] filenameBytes;
            byte[] bs;
            int groupLen;

            //bsOffset = BitConverter.GetBytes(file_offset);
            bsOffset = ProtoCommon.Long2Buff(fileOffset);
            //bsDownBytes = BitConverter.GetBytes(download_bytes);
            bsDownBytes = ProtoCommon.Long2Buff(downloadBytes);
            groupBytes = new byte[ProtoCommon.FdfsGroupNameMaxLen];
            bs = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(groupName);
            filenameBytes = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(remoteFilename);
            for (int i = 0; i < groupBytes.Length; i++)
                groupBytes[i] = (byte)0;
            if (bs.Length <= groupBytes.Length)
            {
                groupLen = bs.Length;
            }
            else
            {
                groupLen = groupBytes.Length;
            }
            Array.Copy(bs, 0, groupBytes, 0, groupLen);

            header = ProtoCommon.PackHeader(ProtoCommon.StorageProtoCmdDownloadFile,
                 bsOffset.Length + bsDownBytes.Length + groupBytes.Length + filenameBytes.Length, (byte)0);
            byte[] wholePkg = new byte[header.Length + bsOffset.Length + bsDownBytes.Length + groupBytes.Length + filenameBytes.Length];
            Array.Copy(header, 0, wholePkg, 0, header.Length);
            Array.Copy(bsOffset, 0, wholePkg, header.Length, bsOffset.Length);
            Array.Copy(bsDownBytes, 0, wholePkg, header.Length + bsOffset.Length, bsDownBytes.Length);
            Array.Copy(groupBytes, 0, wholePkg, header.Length + bsOffset.Length + bsDownBytes.Length, groupBytes.Length);
            Array.Copy(filenameBytes, 0, wholePkg, header.Length + bsOffset.Length + bsDownBytes.Length + groupBytes.Length, filenameBytes.Length);
            (await this.StorageServer.GetSocketAsync()).GetStream().Write(wholePkg, 0, wholePkg.Length);
        }
        /// <summary>
        /// get all metadata items from storage server
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        /// <returns>meta info array</returns>
        public async Task<NameValuePair[]> GetMetadataAsync(string groupName, string remoteFilename)
        {
            bool bNewConnection = await this.NewUpdatableStorageConnectionAsync(groupName, remoteFilename);
            TcpClient storageSocket = await this.StorageServer.GetSocketAsync();

            try
            {
                ProtoCommon.RecvPackageInfo pkgInfo;

                await this.SendPackageAsync(ProtoCommon.StorageProtoCmdGetMetadata, groupName, remoteFilename);
                pkgInfo = ProtoCommon.RecvPackage(storageSocket.GetStream(),
                                             ProtoCommon.StorageProtoCmdResp, -1);

                this.Errno = pkgInfo.Errno;
                if (pkgInfo.Errno != 0)
                {
                    throw new FdfsException("get metadata response error: " + pkgInfo.Errno);
                }

                return ProtoCommon.SplitMetadata(Encoding.GetEncoding(ClientGlobal.Charset).GetString(pkgInfo.Body));
            }
            catch (IOException)
            {
                if (!bNewConnection)
                {
                    this.StorageServer.Close();
                    this.StorageServer = null;
                }

                throw;
            }
            finally
            {
                if (bNewConnection)
                {
                    this.StorageServer.Close();
                    this.StorageServer = null;
                }
            }
        }
        /// <summary>
        /// get file info decoded from the filename, fetch from the storage if necessary
        /// </summary>
        /// <param name="groupName">the group name</param>
        /// <param name="remoteFilename">the filename</param>
        /// <returns>FileInfo object</returns>
        public async Task<FileInfo> GetFileInfoAsync(string groupName, string remoteFilename)
        {
            if (remoteFilename.Length < ProtoCommon.FdfsFilePathLen + ProtoCommon.FdfsFilenameBase64Length
                             + ProtoCommon.FdfsFileExtNameMaxLen + 1)
            {
                this.Errno = ProtoCommon.ErrNoEinval;
                throw new FdfsException("remote file name length error:" + this.Errno);
            }
            byte[] buff = Base64.DecodeAuto(remoteFilename.Substring(ProtoCommon.FdfsFilePathLen,
                /*ProtoCommon.FDFS_FILE_PATH_LEN + */ProtoCommon.FdfsFilenameBase64Length));

            long fileSize = ProtoCommon.Buff2Long(buff, 4 * 2);
            if (((remoteFilename.Length > ProtoCommon.TrunkLogicFilenameLength) ||
                    ((remoteFilename.Length > ProtoCommon.NormalLogicFilenameLength) && ((fileSize & ProtoCommon.TrunkFileMarkSize) == 0))) ||
                    ((fileSize & ProtoCommon.AppenderFileSize) != 0))
            { //slave file or appender file
                FileInfo fi = await this.QueryFileInfoAsync(groupName, remoteFilename);
                return fi;
            }

            FileInfo fileInfo = new FileInfo(fileSize, 0, 0, ProtoCommon.GetIpAddress(buff, 0))
            {
                CreateTimestamp = ProtoCommon.UnixTimestampToDateTime(ProtoCommon.Buff2Int(buff, 4))
            };
            if ((fileSize >> 63) != 0)
            {
                fileSize &= 0xFFFFFFFFL;  //low 32 bits is file size
                fileInfo.FileSize = fileSize;
            }
            fileInfo.Crc32 = ProtoCommon.Buff2Int(buff, 4 * 4);

            return fileInfo;
        }
        /// <summary>
        /// get file info from storage server
        /// </summary>
        /// <param name="groupName">the group name of storage server</param>
        /// <param name="remoteFilename">filename on storage server</param>
        /// <returns>FileInfo</returns>
        public async Task<FileInfo> QueryFileInfoAsync(string groupName, string remoteFilename)
        {
            bool bNewConnection = await this.NewUpdatableStorageConnectionAsync(groupName, remoteFilename);
            TcpClient storageSocket = await this.StorageServer.GetSocketAsync();

            try
            {
                byte[] header;
                byte[] groupBytes;
                byte[] filenameBytes;
                byte[] bs;
                int groupLen;
                ProtoCommon.RecvPackageInfo pkgInfo;

                filenameBytes = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(remoteFilename);
                groupBytes = new byte[ProtoCommon.FdfsGroupNameMaxLen];
                bs = Encoding.GetEncoding(ClientGlobal.Charset).GetBytes(groupName);
                for (int i = 0; i < groupBytes.Length; i++)
                    groupBytes[i] = (byte)0;
                if (bs.Length <= groupBytes.Length)
                {
                    groupLen = bs.Length;
                }
                else
                {
                    groupLen = groupBytes.Length;
                }
                Array.Copy(bs, 0, groupBytes, 0, groupLen);

                header = ProtoCommon.PackHeader(ProtoCommon.StorageProtoCmdQueryFileInfo,
                           +groupBytes.Length + filenameBytes.Length, (byte)0);
                Stream output = storageSocket.GetStream();
                byte[] wholePkg = new byte[header.Length + groupBytes.Length + filenameBytes.Length];
                Array.Copy(header, 0, wholePkg, 0, header.Length);
                Array.Copy(groupBytes, 0, wholePkg, header.Length, groupBytes.Length);
                Array.Copy(filenameBytes, 0, wholePkg, header.Length + groupBytes.Length, filenameBytes.Length);
                output.Write(wholePkg, 0, wholePkg.Length);

                pkgInfo = ProtoCommon.RecvPackage(storageSocket.GetStream(),
                                             ProtoCommon.StorageProtoCmdResp,
                                             3 * ProtoCommon.FdfsProtoPkgLenSize +
                                             ProtoCommon.FdfsIpaddrSize);

                this.Errno = pkgInfo.Errno;
                if (pkgInfo.Errno != 0)
                {
                    throw new FdfsException("query file info response error: " + pkgInfo.Errno);
                }

                long fileSize = ProtoCommon.Buff2Long(pkgInfo.Body, 0);
                int createTimestamp = (int)ProtoCommon.Buff2Long(pkgInfo.Body, ProtoCommon.FdfsProtoPkgLenSize);
                int crc32 = (int)ProtoCommon.Buff2Long(pkgInfo.Body, 2 * ProtoCommon.FdfsProtoPkgLenSize);
                string sourceIpAddr = Encoding.GetEncoding(ClientGlobal.Charset).GetString(pkgInfo.Body, 3 * ProtoCommon.FdfsProtoPkgLenSize, ProtoCommon.FdfsIpaddrSize).Replace("\0", "").Trim();
                return new FileInfo(fileSize, createTimestamp, crc32, sourceIpAddr);
            }
            catch (IOException ex)
            {
                if (!bNewConnection)
                {
                    this.StorageServer.Close();
                    this.StorageServer = null;
                }

                throw ex;
            }
            finally
            {
                if (bNewConnection)
                {
                    this.StorageServer.Close();
                    this.StorageServer = null;
                }
            }
        }
    }
}
